﻿#region Apache 2.0 License header

//   Copyright 2012 Lesley van Zijl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. 

#endregion

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace FavourObjects.ForwardBuilders
{
    internal class ForwardPropertyToFieldBuilder<TTargetClass>
        : ForwardPropertyBuilderBase<TTargetClass>
        where TTargetClass : class
    {
        private readonly FieldInfo _targetField;
        
        public ForwardPropertyToFieldBuilder(TypeBuilder typeBuilder, PropertyBuilder propertyBuilder,
                                             FieldInfo targetField, FieldInfo innerObjectField)
            : base(typeBuilder, propertyBuilder, innerObjectField)
        {
            _targetField = targetField;
        }

        public MethodBuilder CreateGetMethod()
        {
            Type propertyType = _targetField.FieldType;

            MethodBuilder methodBuilder = TypeBuilder.DefineMethod("get_" + _targetField.Name,
                                                                   PropertyMethodAttributes,
                                                                   propertyType,
                                                                   Type.EmptyTypes);

            ILGenerator iLGenerator = methodBuilder.GetILGenerator();

            int getTargetFieldReference = iLGenerator.DeclareLocal(typeof (FieldInfo)).LocalIndex;
            int localTargetTypeField = iLGenerator.DeclareLocal(typeof (TTargetClass)).LocalIndex;

            EmitGetTargetClassType(iLGenerator, localTargetTypeField);

            EmitGetTargetField(iLGenerator, getTargetFieldReference, localTargetTypeField);

            EmitReturnPropertyValue(iLGenerator, propertyType, getTargetFieldReference);

            PropertyBuilder.SetGetMethod(methodBuilder);

            return methodBuilder;
        }

        public MethodBuilder CreateSetMethod()
        {
            Type propertyType = _targetField.FieldType;
            MethodBuilder methodBuilder = TypeBuilder.DefineMethod("set_" + _targetField.Name,
                                                                   PropertyMethodAttributes,
                                                                   null,
                                                                   new[] {propertyType});

            ILGenerator iLGenerator = methodBuilder.GetILGenerator();

            int setTargetFieldReference = iLGenerator.DeclareLocal(typeof (FieldInfo)).LocalIndex;
            int localTargetTypeField = iLGenerator.DeclareLocal(typeof (TTargetClass)).LocalIndex;

            EmitGetTargetClassType(iLGenerator, localTargetTypeField);

            EmitGetTargetField(iLGenerator, setTargetFieldReference, localTargetTypeField);

            EmitSetPropertyValue(iLGenerator, propertyType, setTargetFieldReference);

            PropertyBuilder.SetSetMethod(methodBuilder);

            return methodBuilder;
        }

        private void EmitSetPropertyValue(ILGenerator iLGenerator, Type propertyType, int setTargetFieldReference)
        {
            iLGenerator.Emit(OpCodes.Ldloc_S, setTargetFieldReference);
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Ldfld, InnerObjectFieldInfo);
            iLGenerator.Emit(OpCodes.Ldarg_1);

            MethodInfo setValueMethod = MethodRegistry.GetMethod(typeof (FieldInfo), "SetValue", typeof (object),
                                                                 typeof (object));

            if (propertyType.IsValueType)
            {
                iLGenerator.Emit(OpCodes.Box, propertyType);
            }
            iLGenerator.Emit(OpCodes.Callvirt, setValueMethod);

            iLGenerator.Emit(OpCodes.Ret);
        }


        private void EmitReturnPropertyValue(ILGenerator iLGenerator, Type returnType, int getTargetFieldReference)
        {
            int localPropertyField = iLGenerator.DeclareLocal(returnType).LocalIndex;

            iLGenerator.Emit(OpCodes.Ldloc_S, getTargetFieldReference);
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Ldfld, InnerObjectFieldInfo);

            MethodInfo getValueMethod = MethodRegistry.GetMethod(typeof (FieldInfo), "GetValue", typeof (object));

            iLGenerator.Emit(OpCodes.Callvirt, getValueMethod);

            iLGenerator.Emit(returnType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, returnType);

            iLGenerator.Emit(OpCodes.Stloc_S, localPropertyField);
            iLGenerator.Emit(OpCodes.Ldloc_S, localPropertyField);

            iLGenerator.Emit(OpCodes.Ret);
        }

        private void EmitGetTargetField(ILGenerator iLGenerator, int targetFieldReference, int localTargetTypeField)
        {
            iLGenerator.Emit(OpCodes.Ldloc_S, localTargetTypeField);

            iLGenerator.Emit(OpCodes.Ldstr, _targetField.Name);
            iLGenerator.Emit(OpCodes.Ldc_I4_S, (byte) BindingFlagsForAllMembers);

            MethodInfo getFieldMethod = MethodRegistry.GetMethod(typeof (Type), "GetField", typeof (String),
                                                                 typeof (BindingFlags));
            iLGenerator.Emit(OpCodes.Callvirt, getFieldMethod);

            iLGenerator.Emit(OpCodes.Stloc_S, targetFieldReference);
        }
    }
}